package com.compomics.proteocloud.ui;

import com.compomics.proteocloud.analysis.*;
import com.compomics.proteocloud.io.MascotGenericFile;
import com.compomics.proteocloud.model.*;
import com.compomics.proteocloud.ui.CustomTableCellRenderer;
import com.compomics.proteocloud.ui.ProteoCloudUI;
import com.compomics.proteocloud.util.FormatUtilities;
import com.compomics.util.experiment.biology.Ion;
import com.compomics.util.experiment.biology.Peptide;
import com.compomics.util.experiment.biology.ions.PeptideFragmentIon;
import com.compomics.util.experiment.identification.NeutralLossesMap;
import com.compomics.util.experiment.identification.SpectrumAnnotator;
import com.compomics.util.experiment.identification.matches.IonMatch;
import com.compomics.util.experiment.identification.matches.ModificationMatch;
import com.compomics.util.experiment.massspectrometry.Charge;
import com.compomics.util.experiment.massspectrometry.MSnSpectrum;
import com.compomics.util.experiment.massspectrometry.Peak;
import com.compomics.util.experiment.massspectrometry.Precursor;
import com.compomics.util.gui.interfaces.SpectrumAnnotation;
import com.compomics.util.gui.spectrum.DefaultSpectrumAnnotation;
import com.compomics.util.gui.spectrum.SpectrumPanel;
import com.compomics.util.preferences.UtilitiesUserPreferences;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;
import no.uib.jsparklines.renderers.JSparklinesBarChartTableCellRenderer;
import no.uib.jsparklines.renderers.util.GradientColorCoding;
import org.jdesktop.swingx.JXMultiSplitPane;
import org.jdesktop.swingx.JXTable;
import org.jdesktop.swingx.JXTitledPanel;
import org.jdesktop.swingx.MultiSplitLayout;
import org.jdesktop.swingx.decorator.HighlighterFactory;
import org.jdesktop.swingx.hyperlink.AbstractHyperlinkAction;
import org.jdesktop.swingx.renderer.DefaultTableRenderer;
import org.jdesktop.swingx.renderer.HyperlinkProvider;
import org.jdesktop.swingx.renderer.JXRendererHyperlink;
import org.jdesktop.swingx.table.ColumnControlButton;
import org.jfree.chart.plot.PlotOrientation;

import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.*;
import javax.swing.text.Highlighter;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.net.URI;
import java.sql.SQLException;
import java.text.DecimalFormat;
import java.util.*;
import java.util.List;
import java.util.Map.Entry;

public class ResultPanel extends JPanel {

    private JXTable proteinTbl;
    private JXTable peptideTbl;
    private JXTable psmTbl;
    private List<String> proteinTableToolTips;
    private List<String> peptideTableToolTips;
    private List<String> psmTableToolTips;
    private JPanel spectrumJPanel;
    private JToggleButton aIonsTgl;
    private JToggleButton bIonsTgl;
    private JToggleButton cIonsTgl;
    private JToggleButton xIonsTgl;
    private JToggleButton yIonsTgl;
    private JToggleButton zIonsTgl;
    private JToggleButton waterLossTgl;
    private JToggleButton ammoniumLossTgl;
    private JToggleButton chargeOneTgl;
    private JToggleButton chargeTwoTgl;
    private JToggleButton chargeMoreTgl;
    private JToggleButton precursorTgl;
    private Vector<SpectrumAnnotation> currentAnnotations;
    private SpectrumPanel specPnl;
    private Map<String, MascotGenericFile> querySpectra;
    private JXTable proteinFamTbl;
    private String familyName;
    private CellConstraints cc;
    private DbSearchResult dbSearchResult;
    private ProteoCloudUI mainFrame;
    private JPanel proteinPnl;
    private JPanel peptidePnl;
    private JPanel psmPanel;

    /**
     * Constructor for a database results panel.
     *
     * @param mainFrame a reference to the main frame
     */
    public ResultPanel(ProteoCloudUI mainFrame) {
        this.mainFrame = mainFrame;
        initComponents();
    }

    /**
     * Initialize the components.
     */
    private void initComponents() {
        cc = new CellConstraints();

        // Define layout
        this.setLayout(new FormLayout("5dlu, p:g", "5dlu, t:p:g, 5dlu"));

        proteinPnl = new JPanel();
        proteinPnl.setLayout(new FormLayout("0dlu, p:g", "5dlu, t:p:g, 5dlu"));

        // Setup tables
        setupProteinTableProperties();
        setupPeptideTableProperties();
        setupPsmTableProperties();

        // Scroll panes
        JScrollPane proteinTableScp = new JScrollPane(proteinTbl);
        proteinTableScp.setPreferredSize(new Dimension(100, 160));
        JScrollPane peptideTableScp = new JScrollPane(peptideTbl);
        peptideTableScp.setPreferredSize(new Dimension(100, 160));
        JScrollPane psmTableScp = new JScrollPane(psmTbl);
        psmTableScp.setPreferredSize(new Dimension(100, 160));
        proteinPnl.add(proteinTableScp, cc.xy(2, 2));
        proteinPnl.setBorder(BorderFactory.createTitledBorder("Proteins"));

        // Peptide panel
        peptidePnl = new JPanel();
        peptidePnl.setLayout(new FormLayout("0dlu, p:g", "5dlu, t:p:g, 5dlu"));

        peptidePnl.add(peptideTableScp, cc.xy(2, 2));
        peptidePnl.setBorder(BorderFactory.createTitledBorder("Peptides"));

        // PSM panel
        psmPanel = new JPanel();
        psmPanel.setLayout(new FormLayout("0dlu, p:g", "5dlu, t:p:g, 5dlu"));
        psmPanel.add(psmTableScp, cc.xy(2, 2));
        psmPanel.setBorder(BorderFactory.createTitledBorder("Peptide-Spectrum-Matches"));

        // Build the spectrum overview panel
        JPanel spectrumOverviewPnl = new JPanel(new BorderLayout());
        spectrumJPanel = new JPanel();
        spectrumJPanel.setLayout(new BoxLayout(spectrumJPanel, BoxLayout.LINE_AXIS));
        spectrumJPanel.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
        spectrumJPanel.add(new SpectrumPanel(new double[]{0.0, 100.0}, new double[]{100.0, 0.0}, 0.0, "", ""));

        spectrumOverviewPnl.add(spectrumJPanel, BorderLayout.CENTER);
        spectrumOverviewPnl.add(constructSpectrumFilterPanel(), BorderLayout.EAST);
        spectrumOverviewPnl.setBorder(BorderFactory.createTitledBorder("Spectrum Viewer"));
        String layoutDef =
                "(COLUMN protein (ROW weight=0.0 (COLUMN weight=0.3 (LEAF weight=0.5 name=peptide) (LEAF weight=0.5 name=psm)) (LEAF weight=0.7 name=plot)))";
        MultiSplitLayout.Node modelRoot = MultiSplitLayout.parseModel(layoutDef);

        final JXMultiSplitPane multiSplitPane = new JXMultiSplitPane();
        multiSplitPane.setDividerSize(10);
        multiSplitPane.getMultiSplitLayout().setModel(modelRoot);
        multiSplitPane.add(proteinPnl, "protein");
        multiSplitPane.add(peptidePnl, "peptide");
        multiSplitPane.add(psmPanel, "psm");
        multiSplitPane.add(spectrumOverviewPnl, "plot");
        this.add(multiSplitPane, cc.xy(2, 2));
    }

    /**
     * This method constructs the spectrum filter panel.
     */
    private JPanel constructSpectrumFilterPanel() {

        JPanel spectrumFilterPanel = new JPanel(new FormLayout("5dlu, p, 5dlu", "5dlu, f:p:g, 2dlu, f:p:g, 2dlu, f:p:g, 5dlu, p, 5dlu, f:p:g, 2dlu, f:p:g, 2dlu, f:p:g, 5dlu, p, 5dlu, f:p:g, 2dlu, f:p:g, 5dlu, p, 5dlu, f:p:g, 2dlu, f:p:g, 2dlu, f:p:g, 5dlu, p, 5dlu, f:p:g, 5dlu"));

        Dimension size = new Dimension(50, 25);
        Action action = new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
                updateFilteredAnnotations();
            }
        };

        aIonsTgl = createFilterToggleButton("a", false, "Show a ions", size, action);
        bIonsTgl = createFilterToggleButton("b", true, "Show b ions", size, action);
        cIonsTgl = createFilterToggleButton("c", false, "Show c ions", size, action);
        xIonsTgl = createFilterToggleButton("x", false, "Show x ions", size, action);
        yIonsTgl = createFilterToggleButton("y", true, "Show y ions", size, action);
        zIonsTgl = createFilterToggleButton("z", false, "Show z ions", size, action);
        waterLossTgl = createFilterToggleButton("°", false, "<html>Show H<sub>2</sub>0 losses</html>", size, action);
        ammoniumLossTgl = createFilterToggleButton("*", false, "<html>Show NH<sub>3</sub> losses</html>", size, action);
        chargeOneTgl = createFilterToggleButton("+", true, "Show ions with charge 1", size, action);
        chargeTwoTgl = createFilterToggleButton("++", false, "Show ions with charge 2", size, action);
        chargeMoreTgl = createFilterToggleButton(">2", false, "Show ions with charge >2", size, action);

        precursorTgl = createFilterToggleButton("MH", true, "Show precursor ion", size, action);

        spectrumFilterPanel.add(aIonsTgl, cc.xy(2, 2));
        spectrumFilterPanel.add(bIonsTgl, cc.xy(2, 4));
        spectrumFilterPanel.add(cIonsTgl, cc.xy(2, 6));
        spectrumFilterPanel.add(new JSeparator(JSeparator.HORIZONTAL), cc.xy(2, 8));
        spectrumFilterPanel.add(xIonsTgl, cc.xy(2, 10));
        spectrumFilterPanel.add(yIonsTgl, cc.xy(2, 12));
        spectrumFilterPanel.add(zIonsTgl, cc.xy(2, 14));
        spectrumFilterPanel.add(new JSeparator(JSeparator.HORIZONTAL), cc.xy(2, 16));
        spectrumFilterPanel.add(waterLossTgl, cc.xy(2, 18));
        spectrumFilterPanel.add(ammoniumLossTgl, cc.xy(2, 20));
        spectrumFilterPanel.add(new JSeparator(JSeparator.HORIZONTAL), cc.xy(2, 22));
        spectrumFilterPanel.add(chargeOneTgl, cc.xy(2, 24));
        spectrumFilterPanel.add(chargeTwoTgl, cc.xy(2, 26));
        spectrumFilterPanel.add(chargeMoreTgl, cc.xy(2, 28));
        spectrumFilterPanel.add(new JSeparator(JSeparator.HORIZONTAL), cc.xy(2, 30));
        spectrumFilterPanel.add(precursorTgl, cc.xy(2, 32));

        return spectrumFilterPanel;
    }

    /**
     * Helper method to ease JToggleButton construction.
     *
     * @param title Title of the toggle button
     * @param selected Selected state
     * @param toolTip Used tooltip
     * @param size Button size
     * @param action Performed action
     * @return A JToggleButton with the defined parameters.
     */
    private JToggleButton createFilterToggleButton(String title, boolean selected, String toolTip, Dimension size, Action action) {
        JToggleButton toggleButton = new JToggleButton(action);
        toggleButton.setText(title);
        toggleButton.setSelected(selected);
        toggleButton.setToolTipText(toolTip);
        toggleButton.setMaximumSize(size);
        toggleButton.setMinimumSize(size);
        toggleButton.setPreferredSize(size);
        return toggleButton;
    }

    /**
     * This method sets up the protein results table.
     */
    private void setupProteinTableProperties() {
        // Protein table
        final DefaultTableModel proteinTblMdl = new DefaultTableModel() {
            // instance initializer block
            {
                setColumnIdentifiers(new Object[]{
                        "#",                 //0
                        "Accession",         //1
                        "Description",       //2
                        "Coverage [%]",      //3
                        "Mol Weight [kDa]",          //4
                        "Peptide Count",          //5
                        "Spectral Count",         //6
                        "NSAF [x100]"});            //7
            }

            public boolean isCellEditable(int row, int col) {
                return false;
            }

            public Class<?> getColumnClass(int columnIndex) {
                switch (columnIndex) {
                    case 0:
                    case 5:
                    case 6:
                        return Integer.class;
                    case 3:
                    case 4:
                    case 7:
                        return Double.class;
                    default:
                        return String.class;
                }
            }
        };

        proteinTableToolTips = new ArrayList<String>();
        proteinTableToolTips.add("Protein Index");
        proteinTableToolTips.add("Protein Accession Number");
        proteinTableToolTips.add("Protein Description");
        proteinTableToolTips.add("Protein Seqeunce Coverage (%)");
        proteinTableToolTips.add("Protein Molecular Weight (kDa)");
        proteinTableToolTips.add("Number of Peptides");
        proteinTableToolTips.add("Number of Spectra");
        proteinTableToolTips.add("Normalized Spectral Abundance Factor (Factor x100)");

        proteinTbl = new JXTable(proteinTblMdl) {
            protected JTableHeader createDefaultTableHeader() {
                return new JTableHeader(columnModel) {
                    public String getToolTipText(MouseEvent e) {
                        java.awt.Point p = e.getPoint();
                        int index = columnModel.getColumnIndexAtX(p.x);
                        int realIndex = columnModel.getColumn(index).getModelIndex();
                        String tip = (String) proteinTableToolTips.get(realIndex);
                        return tip;
                    }
                };
            }
        };

        // Create table model
        final TableColumnModel tcm = proteinTbl.getColumnModel();

        AbstractHyperlinkAction<URI> linkAction = new AbstractHyperlinkAction<URI>() {
            public void actionPerformed(ActionEvent ev) {
                try {
                    Desktop.getDesktop()
                            .browse(new URI("http://www.uniprot.org/uniprot/" + target));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        };

        tcm.getColumn(1).setCellRenderer(new DefaultTableRenderer(new HyperlinkProvider(linkAction)) {
            public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
                Component comp = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
                JXRendererHyperlink compLabel = (JXRendererHyperlink) comp;
                compLabel.setHorizontalAlignment(SwingConstants.CENTER);
                return compLabel;
            }
        });
        tcm.getColumn(0).setMinWidth(40);
        tcm.getColumn(0).setMaxWidth(40);
        tcm.getColumn(2).setMinWidth(200);
        tcm.getColumn(2).setMaxWidth(200);
        tcm.getColumn(0).setCellRenderer(new CustomTableCellRenderer(SwingConstants.RIGHT));
        tcm.getColumn(2).setCellRenderer(new CustomTableCellRenderer(SwingConstants.LEFT));
        tcm.getColumn(3).setCellRenderer(new JSparklinesBarChartTableCellRenderer(PlotOrientation.HORIZONTAL, 100.0, new Color(110, 196, 97)));
        ((JSparklinesBarChartTableCellRenderer) tcm.getColumn(3).getCellRenderer()).showNumberAndChart(true, 50, new Font("Arial", Font.PLAIN, 12), 0);


        // Sort the protein table by spectral count
        proteinTbl.setAutoCreateRowSorter(true);
        proteinTbl.getRowSorter().toggleSortOrder(6);
        proteinTbl.getRowSorter().toggleSortOrder(6);

        proteinTbl.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
            public void valueChanged(ListSelectionEvent evt) {
                refreshPeptideTable();
            }
        });

        // Only one row is selectable
        proteinTbl.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        proteinTbl.setSelectionBackground(new Color(130, 207, 250));

        // Add alternate row striping.
        proteinTbl.addHighlighter(HighlighterFactory.createAlternateStriping());

        // Enables column control
        proteinTbl.setColumnControlVisible(true);
    }

    private void setupPeptideTableProperties() {
        peptideTableToolTips = new ArrayList<String>();
        peptideTableToolTips.add("Peptide Index");
        peptideTableToolTips.add("Peptide Sequence");
        peptideTableToolTips.add("Number of Spectra");

        // Peptide table
        final TableModel peptideTableMdl = new DefaultTableModel() {
            // instance initializer block
            {
                setColumnIdentifiers(new Object[]{"#", "Sequence", "No. Spectra"});
            }

            public boolean isCellEditable(int row, int col) {
                return false;
            }

            public Class<?> getColumnClass(int columnIndex) {
                switch (columnIndex) {
                    case 0:
                    case 2:
                        return Integer.class;
                    case 1:
                        return String.class;
                    default:
                        return String.class;
                }
            }
        };

        peptideTbl = new JXTable(peptideTableMdl) {
            protected JTableHeader createDefaultTableHeader() {
                return new JTableHeader(columnModel) {
                    public String getToolTipText(MouseEvent e) {
                        java.awt.Point p = e.getPoint();
                        int index = columnModel.getColumnIndexAtX(p.x);
                        int realIndex = columnModel.getColumn(index).getModelIndex();
                        String tip = (String) peptideTableToolTips.get(realIndex);
                        return tip;
                    }
                };
            }
        };

        TableColumnModel tcm = peptideTbl.getColumnModel();
        tcm.getColumn(0).setCellRenderer(new CustomTableCellRenderer(SwingConstants.RIGHT));
        tcm.getColumn(0).setMinWidth(30);
        tcm.getColumn(0).setMaxWidth(30);
        tcm.getColumn(2).setMinWidth(100);
        tcm.getColumn(2).setMaxWidth(100);
        //tcm.getColumn(2).setCellRenderer(new JSparklinesBarChartTableCellRenderer(PlotOrientation.HORIZONTAL, 1.0, true));

        // Sort the peptide table by the number of peptide hits
        peptideTbl.setAutoCreateRowSorter(true);
        peptideTbl.getRowSorter().toggleSortOrder(2);
        peptideTbl.getRowSorter().toggleSortOrder(2);

        // register list selection listener
        peptideTbl.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
            public void valueChanged(ListSelectionEvent e) {
                refreshPsmTable();
            }
        });

        // Single selection only
        peptideTbl.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        peptideTbl.setSelectionBackground(new Color(130, 207, 250));

        // Add alternate row striping.
        peptideTbl.addHighlighter(HighlighterFactory.createAlternateStriping());

        // Enables column control
        peptideTbl.setColumnControlVisible(true);
    }

    /**
     * This method sets up the PSM results table.
     */
    private void setupPsmTableProperties() {
        psmTableToolTips = new ArrayList<String>();
        psmTableToolTips.add("PSM Index");
        psmTableToolTips.add("Number of Votes (Search Engines)");
        psmTableToolTips.add("X!Tandem Confidence (1 - q-value)");
        psmTableToolTips.add("Omssa Confidence (1 - q-value)");
        psmTableToolTips.add("Crux Confidence (1 - q-value)");
        psmTableToolTips.add("Inspect Confidence (1 - q-value)");
        // PSM table
        final TableModel psmTblMdl = new DefaultTableModel() {
            {
                setColumnIdentifiers(new Object[] {"#", "Votes", "X!Tandem", "Omssa", "Crux", "Inspect" });
            }

            public boolean isCellEditable(int row, int col) {
                return false;
            }

            public Class<?> getColumnClass(int columnIndex) {
                switch (columnIndex) {
                    case 0:
                    case 1:
                        return Integer.class;
                    case 2:
                    case 3:
                    case 4:
                    case 5:
                        return Double.class;
                    default:
                        return String.class;
                }
            }
        };

        psmTbl = new JXTable(psmTblMdl) {
            private Border padding = BorderFactory.createEmptyBorder(0, 2, 0, 2);

            protected JTableHeader createDefaultTableHeader() {
                return new JTableHeader(columnModel) {
                    public String getToolTipText(MouseEvent e) {
                        java.awt.Point p = e.getPoint();
                        int index = columnModel.getColumnIndexAtX(p.x);
                        int realIndex = columnModel.getColumn(index).getModelIndex();
                        String tip = (String) psmTableToolTips.get(realIndex);
                        return tip;
                    }
                };
            }

            @Override
            public Component prepareRenderer(TableCellRenderer renderer,
                                             int row, int column) {
                Component comp = super.prepareRenderer(renderer, row, column);
                if (comp instanceof JComponent) {
                    ((JComponent) comp).setBorder(padding);
                }
                return comp;
            }
        };

        // Get table column model
        final TableColumnModel tcm = psmTbl.getColumnModel();

        tcm.getColumn(0).setMinWidth(30);
        tcm.getColumn(0).setMaxWidth(30);
        tcm.getColumn(0).setCellRenderer(new CustomTableCellRenderer(SwingConstants.RIGHT));
        tcm.getColumn(1).setCellRenderer(new JSparklinesBarChartTableCellRenderer(PlotOrientation.HORIZONTAL, 0.0, 4.0));
        tcm.getColumn(2).setCellRenderer(new JSparklinesBarChartTableCellRenderer(PlotOrientation.HORIZONTAL, 1.0, new Color(110, 196, 97)));
        tcm.getColumn(3).setCellRenderer(new JSparklinesBarChartTableCellRenderer(PlotOrientation.HORIZONTAL, 1.0, new Color(110, 196, 97)));
        tcm.getColumn(4).setCellRenderer(new JSparklinesBarChartTableCellRenderer(PlotOrientation.HORIZONTAL, 1.0, new Color(110, 196, 97)));
        tcm.getColumn(5).setCellRenderer(new JSparklinesBarChartTableCellRenderer(PlotOrientation.HORIZONTAL, 1.0, new Color(110, 196, 97)));
        ((JSparklinesBarChartTableCellRenderer) tcm.getColumn(1).getCellRenderer()).setGradientColoring(GradientColorCoding.ColorGradient.RedBlackBlue);
        ((JSparklinesBarChartTableCellRenderer) tcm.getColumn(1).getCellRenderer()).showNumberAndChart(true, 30, new Font("Arial", Font.PLAIN, 12), 0);
        ((JSparklinesBarChartTableCellRenderer) tcm.getColumn(2).getCellRenderer()).showNumberAndChart(true, 30, new Font("Arial", Font.PLAIN, 12), 0);
        ((JSparklinesBarChartTableCellRenderer) tcm.getColumn(3).getCellRenderer()).showNumberAndChart(true, 30, new Font("Arial", Font.PLAIN, 12), 0);
        ((JSparklinesBarChartTableCellRenderer) tcm.getColumn(4).getCellRenderer()).showNumberAndChart(true, 30, new Font("Arial", Font.PLAIN, 12), 0);
        ((JSparklinesBarChartTableCellRenderer) tcm.getColumn(5).getCellRenderer()).showNumberAndChart(true, 30, new Font("Arial", Font.PLAIN, 12), 0);

        // Auto row sorter
        psmTbl.setAutoCreateRowSorter(true);

        // Register list selection listener
        psmTbl.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
            public void valueChanged(ListSelectionEvent e) {
                refreshPlotAndShowPopup();
            }
        });

        // Only one row is selectable
        psmTbl.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        psmTbl.setSelectionBackground(new Color(130, 207, 250));

        // Add alternate row striping.
        psmTbl.addHighlighter(HighlighterFactory.createAlternateStriping());

        // Enables column control
        psmTbl.setColumnControlVisible(true);
        psmTbl.getColumnControl().setBorder(BorderFactory.createCompoundBorder(
                BorderFactory.createMatteBorder(1, 1, 0, 0, Color.WHITE),
                BorderFactory.createMatteBorder(0, 0, 1, 0, Color.GRAY)));
        psmTbl.getColumnControl().setOpaque(false);
    }



    /**
     * Method to refresh protein table contents.
     */
    protected void refreshProteinTable() {

        int i = 1;
        ((DefaultTableModel) proteinTbl.getModel()).setRowCount(0);

        Map<String, ProteinHit> proteinHits = dbSearchResult.getProteinHits();
        proteinPnl.setBorder(BorderFactory.createTitledBorder("Proteins (" + proteinHits.size() + ")"));
        int maxPeptideCount = 0, maxSpecCount = 0;
        double maxMolWeight = 0.0, maxNSAF = 0.0;

        for (Entry<String, ProteinHit> entry : proteinHits.entrySet()) {

            // Get the protein hit.
            ProteinHit proteinHit = entry.getValue();

            // Calculate NSAF
            double nsaf = ProteinAnalysis.calculateLabelFree(new NormalizedSpectralAbundanceFactor(), dbSearchResult.getProteinHits(), proteinHit);
            proteinHit.setNSAF(nsaf);
            maxPeptideCount = Math.max(maxPeptideCount, proteinHit.getPeptideCount());
            maxSpecCount = Math.max(maxSpecCount, proteinHit.getSpectralCount());
            maxMolWeight = Math.max(maxMolWeight, proteinHit.getMolecularWeight());

            maxNSAF = Math.max(maxNSAF, nsaf);

            ((DefaultTableModel) proteinTbl.getModel()).addRow(new Object[]{
                    i++,
                    proteinHit.getAccession(),
                    proteinHit.getDescription(),
                    proteinHit.getCoverage(),
                    proteinHit.getMolecularWeight(),
                    proteinHit.getPeptideCount(),
                    proteinHit.getSpectralCount(),
                    nsaf
            });
        }
        proteinTbl.getSelectionModel().setSelectionInterval(0, 0);

        // Adjust JSparklines and table cell renderers.
        if (proteinTbl.getRowCount() > 0) {
            TableColumnModel tcm = proteinTbl.getColumnModel();
            tcm.getColumn(4).setCellRenderer(new JSparklinesBarChartTableCellRenderer(PlotOrientation.HORIZONTAL, maxMolWeight, new Color(110, 196, 97)));
            ((JSparklinesBarChartTableCellRenderer) tcm.getColumn(4).getCellRenderer()).showNumberAndChart(true, 50, new Font("Arial", Font.PLAIN, 12), 0);
            tcm.getColumn(5).setCellRenderer(new JSparklinesBarChartTableCellRenderer(PlotOrientation.HORIZONTAL, (double) maxPeptideCount, new Color(110, 196, 97)));
            ((JSparklinesBarChartTableCellRenderer) tcm.getColumn(5).getCellRenderer()).showNumberAndChart(true, 50, new Font("Arial", Font.PLAIN, 12), 0);
            tcm.getColumn(6).setCellRenderer(new JSparklinesBarChartTableCellRenderer(PlotOrientation.HORIZONTAL, (double) maxSpecCount, new Color(110, 196, 97)));
            ((JSparklinesBarChartTableCellRenderer) tcm.getColumn(6).getCellRenderer()).showNumberAndChart(true, 50, new Font("Arial", Font.PLAIN, 12), 0);
            tcm.getColumn(7).setCellRenderer(new JSparklinesBarChartTableCellRenderer(PlotOrientation.HORIZONTAL, maxNSAF, new Color(110, 196, 97)));
            ((JSparklinesBarChartTableCellRenderer) tcm.getColumn(7).getCellRenderer()).showNumberAndChart(true, 50, new Font("Arial", Font.PLAIN, 12), 0);
        }
    }

    /**
     * This method updates the filtered annotations due to the respective selected checkboxes.
     */
    private void updateFilteredAnnotations() {
        if (currentAnnotations != null) {
            specPnl.setAnnotations(filterAnnotations(currentAnnotations));
            specPnl.validate();
            specPnl.repaint();
        }
    }

    /**
     * This method updates the results in the panel.
     *
     * @param dbSearchResult The search result
     */
    public void updateResults(DbSearchResult dbSearchResult) {
        this.dbSearchResult = dbSearchResult;
        refreshProteinTable();
        refreshPeptideTable();
        refreshPsmTable();
    }

    /**
     * This method filters the annotations.
     *
     * @param annotations the annotations to be filtered
     * @return the filtered annotations
     */
    private Vector<SpectrumAnnotation> filterAnnotations(Vector<SpectrumAnnotation> annotations) {

        Vector<SpectrumAnnotation> filteredAnnotations = new Vector<SpectrumAnnotation>();

        JToggleButton[] ionToggles = new JToggleButton[] { aIonsTgl, bIonsTgl,
                cIonsTgl, xIonsTgl, yIonsTgl, zIonsTgl, precursorTgl };
        String ionTokens = "abcxyzM";

        JToggleButton[] miscToggles = new JToggleButton[] { chargeOneTgl,
                chargeMoreTgl, chargeTwoTgl, ammoniumLossTgl, waterLossTgl };
        String[] miscTokens = new String[]  { "+", "+++", "++", "*", "\u00B0" };

        for (SpectrumAnnotation annotation : annotations) {
            String currentLabel = annotation.getLabel();
            boolean useAnnotation = true;
            // check ion type
            for (int i = 0; i < ionToggles.length; i++) {
                if (currentLabel.lastIndexOf(ionTokens.charAt(i)) != -1) {
                    useAnnotation &= ionToggles[i].isSelected();
                }
                if (!useAnnotation) break;
            }

            // check ion charge + ammonium and water-loss
            if (useAnnotation) {
                for (int i = 0; i < miscToggles.length; i++) {
                    if (currentLabel.lastIndexOf(miscTokens[i]) != -1) {
                        useAnnotation &= miscToggles[i].isSelected();
                    }
                    if (!useAnnotation) break;
                }
            }

            // If not used, don't add the annotation.
            if (useAnnotation) {
                filteredAnnotations.add(annotation);
            }
        }

        return filteredAnnotations;
    }

    /**
     * Method to refresh peptide table contents.
     */
    protected void refreshPeptideTable() {
        ((DefaultTableModel) peptideTbl.getModel()).setRowCount(0);

        int protRow = proteinTbl.getSelectedRow();
        if (protRow != -1 && !dbSearchResult.isEmpty()) {
            String actualAccession = (String) proteinTbl.getValueAt(protRow, proteinTbl.convertColumnIndexToView(1));
            ProteinHit proteinHit = dbSearchResult.getProteinHit(actualAccession);
            List<PeptideHit> peptideHits = proteinHit.getPeptideHitList();
            peptidePnl.setBorder(BorderFactory.createTitledBorder("Peptides (" + peptideHits.size() + ")"));

            int i = 1, maxSpecCount = 0;
            for (PeptideHit peptideHit : peptideHits) {
                int specCount = peptideHit.getSpectrumMatches().size();
                maxSpecCount = Math.max(maxSpecCount, specCount);
                ((DefaultTableModel) peptideTbl.getModel()).addRow(new Object[]{
                        i++,
                        peptideHit.getSequence(),
                        specCount});
            }
            peptideTbl.getSelectionModel().setSelectionInterval(0, 0);

            TableColumnModel tcm = peptideTbl.getColumnModel();
            tcm.getColumn(2).setCellRenderer(new JSparklinesBarChartTableCellRenderer(PlotOrientation.HORIZONTAL, (double) maxSpecCount, new Color(110, 196, 97)));
            ((JSparklinesBarChartTableCellRenderer) tcm.getColumn(2).getCellRenderer()).showNumberAndChart(true, 40, new Font("Arial", Font.PLAIN, 12), 0);
        }
    }

    /**
     * Method to refresh PSM table contents.
     */
    protected void refreshPsmTable() {
        ((DefaultTableModel) psmTbl.getModel()).setRowCount(0);

        int protRow = proteinTbl.getSelectedRow();
        if (protRow != -1 && !dbSearchResult.isEmpty()) {
            String accession = (String) proteinTbl.getValueAt(protRow, proteinTbl.convertColumnIndexToView(1));
            int pepRow = peptideTbl.getSelectedRow();
            if (pepRow != -1) {
                String sequence = (String) peptideTbl.getValueAt(pepRow, peptideTbl.convertColumnIndexToView(1));
                PeptideHit peptideHit = dbSearchResult.getProteinHits().get(accession).getPeptideHits().get(sequence);

                DefaultTableModel psmTblMdl = (DefaultTableModel) psmTbl.getModel();
                int i = 1, maxVotes = 0;
                List<SpectrumMatch> spectrumMatches = peptideHit.getSpectrumMatches();
                psmPanel.setBorder(BorderFactory.createTitledBorder("Peptide-Spectrum-Matches (" + spectrumMatches.size() + ")"));

                for (SpectrumMatch sm : spectrumMatches) {
                    PeptideSpectrumMatch psm = (PeptideSpectrumMatch) sm;
                    List<SearchHit> searchHits = psm.getSearchHits();
                    double[] qValues = { 0.0, 0.0, 0.0, 0.0 };
                    for (SearchHit searchHit : searchHits) {
                        switch (searchHit.getType()) {
                            case XTANDEM:
                                qValues[0] = 1.0 - searchHit.getQvalue().doubleValue();
                                break;
                            case OMSSA:
                                qValues[1] = 1.0 - searchHit.getQvalue().doubleValue(); break;
                            case CRUX:
                                qValues[2] = 1.0 - searchHit.getQvalue().doubleValue(); break;
                            case INSPECT:
                                qValues[3] = 1.0 - searchHit.getQvalue().doubleValue(); break;
                        }
                    }

                    maxVotes = Math.max(maxVotes, psm.getVotes());
                    psmTblMdl.addRow(new Object[] {
                            i++,
                            psm.getVotes(),
                            qValues[0],
                            qValues[1],
                            qValues[2],
                            qValues[3]});
                }
                psmTbl.getSelectionModel().setSelectionInterval(0, 0);
            }
        }
    }


    protected void refreshPlotAndShowPopup() {
        int protRow = proteinTbl.getSelectedRow();
        if (protRow != -1) {
            int pepRow = peptideTbl.getSelectedRow();
            if (pepRow != -1) {
                int psmRow = psmTbl.getSelectedRow();
                if (psmRow != -1) {
                    // clear the spectrum panel
                    while (spectrumJPanel.getComponents().length > 0) {
                        spectrumJPanel.remove(0);
                    }
                    // get the list of spectrum matches
                    String actualAccession = (String) proteinTbl.getValueAt(protRow, proteinTbl.convertColumnIndexToView(1));
                    String sequence = (String) peptideTbl.getValueAt(pepRow, peptideTbl.convertColumnIndexToView(1));
                    int index = psmTbl.convertRowIndexToModel(psmRow);
                    PeptideSpectrumMatch psm = (PeptideSpectrumMatch) dbSearchResult.getProteinHits().get(actualAccession).getPeptideHits().get(sequence).getSpectrumMatches().get(index);
                    // grab corresponding spectrum from the database and display it
                    try {
                        specPnl = new SpectrumPanel(psm.getMascotGenericFile());
                        specPnl.showAnnotatedPeaksOnly(true);

                        spectrumJPanel.add(specPnl, cc.xy(2, 2));
                        spectrumJPanel.validate();
                        spectrumJPanel.repaint();

                        Fragmentizer fragmentizer = new Fragmentizer(sequence, Masses.getInstance(), psm.getCharge());
                        addSpectrumAnnotations(fragmentizer.getFragmentIons());
                    } catch (SQLException e) {
                        e.printStackTrace();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    /**
     * Method to add annotations to spectrum plot.
     * @param fragmentIons
     */
    private void addSpectrumAnnotations(Map<String, FragmentIon[]> fragmentIons) {
        currentAnnotations = new Vector<SpectrumAnnotation>();
        Set<Entry<String, FragmentIon[]>> entrySet = fragmentIons.entrySet();
        int i = 0;
        for (Entry<String, FragmentIon[]> entry : entrySet) {
            FragmentIon[] ions = entry.getValue();

            for (FragmentIon ion : ions) {
                int ionNumber = ion.getNumber();
                double mzValue = ion.getMZ();
                Color color;
                if (i % 2 == 0) {
                    color = Color.BLUE;
                } else {
                    color = Color.BLACK;
                }

                // Use standard ion type names, such as y5++
                String ionDesc = ion.getLetter();
                if (ionNumber > 0) {
                    ionDesc += ionNumber;
                }
                if (ion.getCharge() > 1) {
                    for (int j = 0; j < ion.getCharge(); j++) {
                        ionDesc += "+";
                    }
                }
                // TODO: Get fragment ion mass error from db or settings file!
                currentAnnotations.add(new DefaultSpectrumAnnotation(mzValue, 0.5, color, ionDesc));
            }
            i++;
        }
        //allAnnotations.put(currentSelectedPeptide, currentAnnotations);

        if(currentAnnotations.size() > 0 ){
            specPnl.setAnnotations(filterAnnotations(currentAnnotations));
            specPnl.validate();
            specPnl.repaint();
        }

    }
}
